package com.kamistoat.meimeimall.meimeimallproduct.service.impl;

import com.kamistoat.meimeimall.common.constant.DatabaseConstant;
import com.kamistoat.meimeimall.common.utils.PageUtils;
import com.kamistoat.meimeimall.common.utils.Query;
import com.alibaba.fastjson.JSON;
import com.kamistoat.meimeimall.common.To.EsTo.SpuUpTo;
import com.kamistoat.meimeimall.common.To.productTo.SkuMemberPriceTo;
import com.kamistoat.meimeimall.common.To.productTo.SkuReductionTo;
import com.kamistoat.meimeimall.common.To.productTo.SpuBoundsTo;
import com.kamistoat.meimeimall.common.To.productTo.SpuInfoTo;
import com.kamistoat.meimeimall.common.To.wareTo.HasStockMapTo;
import com.kamistoat.meimeimall.common.annotation.WithCache;
import com.kamistoat.meimeimall.common.constant.ProductConstant;
import com.kamistoat.meimeimall.common.dubboInterface.CouponDubboInterface;
import com.kamistoat.meimeimall.common.dubboInterface.SearchDubboInterface;
import com.kamistoat.meimeimall.common.dubboInterface.WareDubboInterface;
import com.kamistoat.meimeimall.common.exception.BizCodeEnum;
import com.kamistoat.meimeimall.common.exception.RRException;
import com.kamistoat.meimeimall.common.utils.R;
import com.kamistoat.meimeimall.meimeimallproduct.dao.*;
import com.kamistoat.meimeimall.meimeimallproduct.entity.*;
import com.kamistoat.meimeimall.meimeimallproduct.service.*;
import com.kamistoat.meimeimall.meimeimallproduct.vo.spuSave.*;
import org.apache.dubbo.config.annotation.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {

    protected Logger logger = LoggerFactory.getLogger(SpuInfoServiceImpl.class);

    @Autowired
    AttrService attrService;
    @Autowired
    SpuInfoDescService spuInfoDescService;
    @Autowired
    SpuImagesService spuImagesService;
    @Autowired
    ProductAttrValueServiceImpl productAttrValueService;
    @Autowired
    SkuInfoService skuInfoService;
    @Autowired
    SkuImagesService skuImagesService;
    @Autowired
    SkuSaleAttrValueService skuSaleAttrValueService;
    @Autowired
    BrandService brandService;
    @Autowired
    CategoryService categoryService;
    @Autowired
    SpuInfoDao spuInfoDao;
    @Autowired
    CategoryBrandRelationDao categoryBrandRelationDao;
    @Autowired
    AttrDao attrDao;
    @Autowired
    SpuImagesDao spuImagesDao;
    @Autowired
    SpuInfoDescDao spuInfoDescDao;
    @Autowired
    ProductAttrValueDao productAttrValueDao;

    @Reference
    CouponDubboInterface couponDubboInterface;
    @Reference
    SearchDubboInterface searchDubboInterface;
    @Reference
    WareDubboInterface wareDubboInterface;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                new QueryWrapper<SpuInfoEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * // TODO 如果中途失败，回滚该如何处理？
     * 自己编写的方法，保存SpuSaveVo
     * 在这个方法中会调用很多其他Service层的功能，甚至是跨模块的操作
     *
     * @param spuSaveVo 前端的spuSaveVo
     */
    @Override
    @Transactional
    public void saveSpuInfo(SpuSaveVo spuSaveVo) {
        // ①、先把SpuSaveVo的基本SPU信息保存下来。
        // 基本的信息应该存放在pms_spu_info数据表中。
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(spuSaveVo, spuInfoEntity);  // 此时spuInfoEntity中的更新时间、创建时间两个字段没有赋值
        spuInfoEntity.setCreateTime(new Date());
        spuInfoEntity.setUpdateTime(new Date());
        this.saveBaseSpuInfo(spuInfoEntity);  // 保存完成后，spuInfoEntity自动生成自增主键，使用spuInfoEntity.getId()获取SpuId
        // 当前SPU的SKU商品图片集应该保存在pms_spu_images数据表中。
        // 每一张图片都要单独存一行，因为有客制化参数。
        List<String> images = spuSaveVo.getImages();
        spuImagesService.saveSpuImages(spuInfoEntity.getId(), images);
        // 当前SPU的整体描述图片应该保存在pms_spu_info_desc数据表中。
        // 由于一个商品可能对应太多的图片，如果按照以往，一行保存一个SPU与图片路径的对应关系，占用太多表数据
        // 考虑到这些图片都是平等的，毫无区别科研。因此图片列表中所有图片路径拼装成一个长String串，只存一行
        List<String> decript = spuSaveVo.getDecript();
        SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
        descEntity.setSpuId(spuInfoEntity.getId());
        descEntity.setDecript(String.join(",", decript));
        spuInfoDescService.saveSpuInfoDesc(descEntity);
        // ②、把SpuSaveVo中的基础属性信息保存下来，这一步保存的信息来自于新增商品时第二页填写的内容。
        // 基础属性信息在SpuSaveVo中以一级字段baseAttrs，嵌套BaseAttrs类来存放。
        // 基础属性应该保存在`pms_product_attr_value`数据表中。
        // 注意并没有销售属性，销售属性根据SKU不同也会不同，因此是在保存SKU时保存。
        List<BaseAttrs> baseAttrsList = spuSaveVo.getBaseAttrs();
        productAttrValueService.saveSpuAttr(spuInfoEntity.getId(), baseAttrsList);
        // 使用远程接口，保存SPU积分信息，使用模块传输对象类To来进行跨模块数据传输
        SpuBoundsTo spuBoundsTo = new SpuBoundsTo();
        BeanUtils.copyProperties(spuSaveVo.getBounds(), spuBoundsTo);
        spuBoundsTo.setSpuId(spuInfoEntity.getId());
        R saveSpuBoundsR = couponDubboInterface.saveSpuBounds(spuBoundsTo);
        if (saveSpuBoundsR.getCode() != 0) {
            throw new RRException(BizCodeEnum.SAVE_SPU_BOUNDS_ERROR.getMessage(), BizCodeEnum.SAVE_SPU_BOUNDS_ERROR.getCode());
        }
        // ③、保存当前SPU下的所有SKU信息，SKU信息通过一个列表存放在SpuSaveVo的一级字段skus下，嵌套Skus类。
        // 因此就是相当于遍历skus字段，对每一个Skus类进行保存，同样是：
        // 当前SKU基本信息保存在`pms_sku_info`数据表中。
        // 由于SKU基本信息中需要用到图片信息，
        // 直接把当前SKU使用①中上传的SPU图片集中个的哪张图片作为自身商品图，保存在`pms_sku_images`数据表中。
        List<Skus> skusList = spuSaveVo.getSkus();
        if (skusList != null && skusList.size() > 0) {
            skusList.forEach(skus -> {
                SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
                BeanUtils.copyProperties(skus, skuInfoEntity);
                skuInfoEntity.setSpuId(spuInfoEntity.getId());
                skuInfoEntity.setBrandId(spuInfoEntity.getBrandId());
                skuInfoEntity.setCatalogId(spuInfoEntity.getCatalogId());
                skuInfoEntity.setSaleCount(0L);
                // 如何确定每一个SKU使用哪一个图片作为默认图片？
                // 查看当前skus下的images列表字段下，哪一个Images变量的defaultImg为1
                String defaultImage = "";
                // 这里不能用forEach来修改defaultImage，lambda表达式不允许修改外部变量
                for (Images images1 : skus.getImages()) {
                    if (images1.getDefaultImg() == 1) {
                        defaultImage = images1.getImgUrl();
                    }
                }
                skuInfoEntity.setSkuDefaultImg(defaultImage);
                skuInfoService.saveSkuInfo(skuInfoEntity);
                // 直接在这里把`pms_sku_images`保存了
                List<SkuImagesEntity> skuImagesEntityList = skus.getImages().stream().map((images1 -> {
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    BeanUtils.copyProperties(images1, skuImagesEntity);
                    skuImagesEntity.setSkuId(skuInfoEntity.getSkuId());
                    return skuImagesEntity;
                })).filter(skuImagesEntity -> {
                    // 只保存SKU选中的SPU图片，过滤掉‘位于SPU图集中但是没有被当前SKU选中的图片’
                    // 下方的判断式返回0就删除当前的skuImagesEntity，返回1就保留
                    return !StringUtils.isEmpty(skuImagesEntity.getImgUrl());
                }).collect(Collectors.toList());
                skuImagesService.saveBatch(skuImagesEntityList);
                // 接着保存Sku销售属性信息，保存在`pms_sku_sale_attr_value`数据表中。
                List<Attr> saleAttrList = skus.getAttr();
                List<SkuSaleAttrValueEntity> skuSaleAttrValueEntityList = saleAttrList.stream().map(saleAttr -> {
                    SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
                    BeanUtils.copyProperties(saleAttr, skuSaleAttrValueEntity);
                    skuSaleAttrValueEntity.setSkuId(skuInfoEntity.getSkuId());
                    return skuSaleAttrValueEntity;
                }).collect(Collectors.toList());
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntityList);
                // 使用远程接口，保存SKU积分信息，使用模块传输对象类To来进行跨模块数据传输
                // 首先判断，如果出现无折扣、无满减、无会员价，直接不传输
                if (skus.getFullCount() > 0 || skus.getFullPrice().compareTo(new BigDecimal("0")) == 1 ||
                        (skus.getMemberPrice() != null || skus.getMemberPrice().size() > 0)) {
                    SkuReductionTo skuReductionTo = new SkuReductionTo();
                    BeanUtils.copyProperties(skus, skuReductionTo);
                    // 虽然SkuMemberPriceTo和MemberPrice是完全一样的，这里还是用了两个类，在这里也做了一次流处理更换类
                    List<SkuMemberPriceTo> skuMemberPriceToList = skus.getMemberPrice().stream().map(memberPrice -> {
                        SkuMemberPriceTo skuMemberPriceTo = new SkuMemberPriceTo();
                        BeanUtils.copyProperties(memberPrice, skuMemberPriceTo);
                        return skuMemberPriceTo;
                    }).collect(Collectors.toList());
                    skuReductionTo.setMemberPrice(skuMemberPriceToList);
                    skuReductionTo.setSkuId(skuInfoEntity.getSkuId());
                    R saveSkuReductionR = couponDubboInterface.saveSkuReduction(skuReductionTo);
                    if (saveSkuReductionR.getCode() != 0) {
                        throw new RRException(BizCodeEnum.SAVE_SKU_REDUCTION_ERROR.getMessage(), BizCodeEnum.SAVE_SKU_REDUCTION_ERROR.getCode());
                    }
                }
            });
        }
    }

    /**
     * 并非是Controller接口方法，仅仅是在为了在saveSpuInfo()中调用
     *
     * @param spuInfoEntity
     */
    @Override
    public void saveBaseSpuInfo(SpuInfoEntity spuInfoEntity) {
        baseMapper.insert(spuInfoEntity);
    }

    /**
     * 自己编写的方法，用于根据三级分类id、品牌id、关键字、商品状态查询Spu
     *
     * @param params 参数，内含分页参数和三级分类id、品牌id、关键字、商品状态
     * @return 分页封装
     */
    @Override
    public PageUtils queryPageBySpuInfo(Map<String, Object> params) {
        QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<SpuInfoEntity>();
        // 三级分类和品牌如果不选择，传0
        // 商品状态不选择，传空。关键字不输入，传空
        if (!StringUtils.isEmpty(params.get("catelogId")) && !params.get("catelogId").equals("0")) {
            wrapper.eq("catalog_id", params.get("catelogId"));
        }
        if (!StringUtils.isEmpty(params.get("brandId")) && !params.get("brandId").equals("0")) {
            wrapper.eq("brand_id", params.get("brandId"));
        }
        if (!StringUtils.isEmpty(params.get("status"))) {
            wrapper.eq("publish_status", params.get("status"));
        }
        if (!StringUtils.isEmpty(params.get("key"))) {
            wrapper.and(obj -> {
                obj.eq("id", params.get("key")).or().
                        like("spu_name", params.get("key")).or().
                        like("spu_description", params.get("key"));
            });
        }
        IPage<SpuInfoEntity> skuInfoEntityIPage = this.page(new Query<SpuInfoEntity>().getPage(params), wrapper);
        return new PageUtils(skuInfoEntityIPage);
    }

    /**
     * 自己编写的方法，将spu上架
     * 将spu的状态更改为已上架
     * 将spu下的所有sku的对应信息保存到ES中
     *
     * @param spuId spuId
     */
    @Override
    @Transactional
    public void spuUp(Long spuId) {
        // 不要再最开始就修改spu状态，先把ES的数据保存好，再修改spu状态
//        SpuInfoEntity spuInfoEntity = this.getById(spuId);
//        spuInfoEntity.setPublishStatus(ProductConstant.SpuPublishStatus.SPU_PUBLISH_STATUS_PUBLISH.getCode());

        // 查出spu对应的基础属性，返回一个list，因为所有的spuUpTo用的都是相同的，所以可以提前出来做
        List<ProductAttrValueEntity> productAttrValueEntityList =  // spu与Attr的关系直接是有一张数据表存放的
                productAttrValueService.list(
                        new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
        // 流处理返回一个List<spuAttrs>，直接保存到每个spuUpTo中
        // 注意，还涉及到一个判断属性是否为 “可检索属性”
        List<SpuUpTo.spuAttrs> spuAttrsList = productAttrValueEntityList.stream().map(productAttrValueEntity -> {
            SpuUpTo.spuAttrs spuAttrs = new SpuUpTo.spuAttrs();
            spuAttrs.setAttrId(productAttrValueEntity.getAttrId());
            spuAttrs.setAttrName(productAttrValueEntity.getAttrName());
            // 数据库中 值;值;值 形式的String数据，要分割后处理成列表
            List<SpuUpTo.attrValue> attrValueList = Arrays.asList(productAttrValueEntity.getAttrValue().split(";")).stream().map(value -> {
                SpuUpTo.attrValue attrValue = new SpuUpTo.attrValue();
                attrValue.setAttrValue(value);
                return attrValue;
            }).collect(Collectors.toList());
            spuAttrs.setAttrValues(attrValueList);
            // BeanUtils.copyProperties(productAttrValueEntity, spuAttrs);
            return spuAttrs;
        }).filter(spuAttrs -> {
            // 再加一个filter，将attr数据表中searchtype=0的删除掉
            return attrService.getById(spuAttrs.getAttrId()).getSearchType() == 1;
        }).collect(Collectors.toList());

        // 先根据spuId查出一个SkuEntity列表
        List<SkuInfoEntity> skuList =
                skuInfoService.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));  // 查出spu对应的所有sku信息，返回一个list
        // 获得skuId列表
        List<Long> skuIdList = skuList.stream().map(skuInfoEntity -> {
            return skuInfoEntity.getSkuId();
        }).collect(Collectors.toList());
        if (skuIdList == null && skuIdList.size() == 0) {
            return;
        }
        // 调用远程接口，一次性查询当前spu下的sku是否有库存。
        // 最好不要在下面的stream中查，调用多次远程接口会拖慢速度
        // 用一个try防止出现异常
        Map<Long, Boolean> skuHasStock = new HashMap<>();
        R hasStockR = wareDubboInterface.hasStock(skuIdList);
        if (hasStockR.getCode() == 0) {
            HasStockMapTo hasStockMapTo = JSON.parseObject((String) hasStockR.get("hasStockMapTo"), HasStockMapTo.class);
            skuHasStock = hasStockMapTo.getHasStockMap();
        }

        // 制作 List<SpuUpTo>
        Map<Long, Boolean> finalSkuHasStock = skuHasStock;
        List<SpuUpTo> spuUpToList = skuList.stream().map(skuInfoEntity -> {
            SpuUpTo spuUpTo = new SpuUpTo();
            BeanUtils.copyProperties(skuInfoEntity, spuUpTo);  // 先将对应的属性复制
            // 需要单独处理：BrandName/BrandImg/CatalogName/Attrs/hasStock/hotScore
            BrandEntity brand = brandService.getById(spuUpTo.getBrandId());  // 查出品牌对应的信息
            spuUpTo.setBrandName(brand.getName());
            spuUpTo.setBrandImg(brand.getLogo());
            CategoryEntity catelog = categoryService.getById(spuUpTo.getCatalogId());  // 查出三级分类信息
            spuUpTo.setCatalogName(catelog.getName());
            spuUpTo.setAttrs(spuAttrsList);  // 保存属性列表
            if (finalSkuHasStock == null) {  // 保存库存信息
                spuUpTo.setHasStock(true);
            } else {
                spuUpTo.setHasStock(finalSkuHasStock.get(spuUpTo.getSkuId()));
            }
            // TODO 热度评分，需要算法实现
            spuUpTo.setHotScore(0L);
            return spuUpTo;
        }).collect(Collectors.toList());
        R spuUpToESR = searchDubboInterface.spuUpToES(spuUpToList);
        if (spuUpToESR.getCode() == 0) {
            // ES数据保存成功，修改spu状态
            SpuInfoEntity spuInfoEntity = this.getById(spuId);
            spuInfoEntity.setPublishStatus(ProductConstant.SpuPublishStatus.SPU_PUBLISH_STATUS_PUBLISH.getCode());
            this.updateById(spuInfoEntity);
        } else {
            throw new RRException(spuUpToESR.getMsg(), spuUpToESR.getCode());
        }
    }

    /**
     * 自己编写的方法，将spu下架
     * 将spu的状态更改为已下架
     * 将spu下的所有sku的对应信息从ES中删除
     *
     * @param spuId spuId
     */
    @Override
    public void spuDown(Long spuId) {
        List<SkuInfoEntity> skuInfoEntityList = skuInfoService.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
        List<Long> skuIdList = skuInfoEntityList.stream().map(skuInfoEntity -> {
            return skuInfoEntity.getSkuId();
        }).collect(Collectors.toList());
        // 调用远程接口从ES中删除数据
        R skuDownToESR = searchDubboInterface.skuDownToES(skuIdList);
        if (skuDownToESR.getCode() == 0) {
            // ES数据删除成功，修改spu状态为以下架
            SpuInfoEntity spuInfoEntity = this.getById(spuId);
            spuInfoEntity.setPublishStatus(ProductConstant.SpuPublishStatus.SPU_PUBLISH_STATUS_CUTDOWN.getCode());
            this.updateById(spuInfoEntity);
        } else {
            throw new RRException(skuDownToESR.getMsg(), skuDownToESR.getCode());
        }
    }

    /**
     * 带缓查询指定Id的SpuInfo
     */
    @Override
    @WithCache(prefix = "product:spu:info:#spuId", isList = false, returnType = SpuInfoTo.class)
    public SpuInfoTo getSpuInfoById(Long spuId) {
        SpuInfoEntity spuInfoEntity = this.getById(spuId);
        SpuInfoTo spuInfoTo = new SpuInfoTo();
        BeanUtils.copyProperties(spuInfoEntity, spuInfoTo);
        return spuInfoTo;
    }

    /**
     * 创建测试数据
     *
     * @param spuPerBrand
     */
    @Override
    public void constructTestSpuData(Integer spuPerBrand) {
        List<CategoryBrandRelationEntity> relations = categoryBrandRelationDao.selectAllRelations();
        logger.info("为" + relations.size() + "条品牌-菜单记录生成" + relations.size() * spuPerBrand + "条Spu数据");
        List<SpuInfoEntity> infos = new ArrayList<>();
        List<SpuInfoDescEntity> descs = new ArrayList<>();
        List<SpuImagesEntity> images = new ArrayList<>();
        List<ProductAttrValueEntity> baseAttrs = new ArrayList<>();

        for (CategoryBrandRelationEntity relation : relations) {
            for (int preNum = 1; preNum <= spuPerBrand; preNum++) {
                SpuInfoEntity var1 = new SpuInfoEntity();
                var1.setSpuName("品牌" + relation.getBrandId() + "分类" + relation.getCatelogId() + "测试SPU商项" + preNum);
                var1.setBrandId(relation.getBrandId());
                var1.setCatalogId(relation.getCatelogId());
                var1.setSpuDescription("品牌" + relation.getBrandId() + "分类" + relation.getCatelogId() + "测试SPU商项" + preNum + "详情");
                var1.setWeight(new BigDecimal("1.00"));
                var1.setCreateTime(new Date());
                var1.setUpdateTime(new Date());
                var1.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
                infos.add(var1);
            }
        }

        spuInfoDao.insertBatch(infos);
        logger.info("生成{}条SpuInfo数据", infos.size());

        for (SpuInfoEntity info : infos) {
            SpuImagesEntity var2 = new SpuImagesEntity();
            var2.setSpuId(info.getId());
            var2.setImgName(info.getSpuName() + "题图");
            var2.setImgUrl("http://test.pic");
            var2.setDefaultImg(1);
            var2.setCreateTime(new Date());
            var2.setUpdateTime(new Date());
            var2.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
            images.add(var2);

            SpuInfoDescEntity var3 = new SpuInfoDescEntity();
            var3.setSpuId(info.getId());
            var3.setDecript("<script>layout</script>");
            var3.setCreateTime(new Date());
            var3.setUpdateTime(new Date());
            var3.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
            descs.add(var3);

            List<AttrEntity> categoryBaseAttrs = attrDao.selectBaseAttrByCategoryId(info.getCatalogId());
            for (AttrEntity attr : categoryBaseAttrs) {
                ProductAttrValueEntity var4 = new ProductAttrValueEntity();
                var4.setSpuId(info.getId());
                var4.setAttrId(attr.getAttrId());
                var4.setAttrName(attr.getAttrName());
                var4.setAttrValue(attr.getValueSelect().split(";")[(int) (Math.random() * (3))]);
                var4.setCreateTime(new Date());
                var4.setUpdateTime(new Date());
                var4.setDelete(DatabaseConstant.UN_LOGISTIC_DELETED);
                baseAttrs.add(var4);
            }
        }
        spuImagesDao.insertBatch(images);
        spuInfoDescDao.insertBatch(descs);
        productAttrValueDao.insertBatch(baseAttrs);
        logger.info("生成{}条SpuInfo图片和描述及属性", infos.size());
    }
}